// Copyright (c) 2009 Mike Haight, Andy Kipp
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.


#include "Window.h"


using namespace HandBanana::Core;
using namespace HandBanana::Utilities;


std::map<HWND, Window*> Window::gWindowTable;


Window::Window(const std::wstring& argName, const HandBanana::Core::Rectangle& argWindowRect)
{
    // Register a window class
    {
        // Create the class name
        std::wostringstream classNameStream;
        classNameStream << L"HandBananaWindow<" << HashCode() << L">" << std::ends;

        // Log the class name
        std::wstring className = classNameStream.str();
        Logger::Info(className);

        // Create the class
        WNDCLASSEXW windowClass;
        windowClass.cbSize = sizeof(windowClass);
        windowClass.cbClsExtra = 0;
        windowClass.cbWndExtra = 0;
        windowClass.hbrBackground = NULL;
        windowClass.hCursor = NULL;
        windowClass.hIcon = NULL;
        windowClass.hIconSm = NULL;
        windowClass.hInstance = GetModuleHandleW(NULL);
        windowClass.lpfnWndProc = RaiseWindowMessage;
        windowClass.lpszClassName = className.c_str();
        windowClass.lpszMenuName = NULL;
        windowClass.style = 0;
        mClassAtom = ::RegisterClassExW(&windowClass);

        // Check for NULL
        THROW_IF_NULL(mClassAtom, "RegisterClassExW");
    }

    // Create a window
    {
        mWindowHandle = ::CreateWindowExW(
            (NULL),
            (LPCWSTR)MAKELONG(mClassAtom, 0),
            (argName.c_str()),
            (WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU),
            (int)(argWindowRect.X()),
            (int)(argWindowRect.Y()),
            (int)(argWindowRect.Width()),
            (int)(argWindowRect.Height()),
            (NULL),
            (NULL),
            (GetModuleHandle(NULL)),
            (NULL));

        THROW_IF_NULL(mWindowHandle, "CreateWindowExW");
    }

    RECT clientRect = {0};
    ::GetClientRect(mWindowHandle, &clientRect);

    ::SetWindowPos(
        mWindowHandle,
        NULL,
        0,
        0,
        2 * (int)argWindowRect.Width() - (clientRect.right - clientRect.left),
        2 * (int)argWindowRect.Height() - (clientRect.bottom - clientRect.top),
        SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW);

    ::ShowWindow(mWindowHandle, SW_SHOW);

    // Save the window to the dictionary
    gWindowTable[mWindowHandle] = this;
}


Window::~Window(void)
{
    this->Destroy();
}


HWND Window::Handle(void) const
{
    return mWindowHandle;
}


int Window::ViewWidth(void) const
{
    RECT rect;
    ::GetClientRect(mWindowHandle, &rect);
    return rect.right - rect.left;
}


int Window::ViewHeight(void) const
{
    RECT rect;
    ::GetClientRect(mWindowHandle, &rect);
    return rect.bottom - rect.top;
}


int Window::X(void) const
{
    RECT rect;
    ::GetWindowRect(mWindowHandle, &rect);
    return rect.left;
}


void Window::X(int value)
{
    ::SetWindowPos(mWindowHandle, NULL, value, Y(), NULL, NULL, SWP_NOZORDER | SWP_NOSIZE);
}


int Window::Y(void) const
{
    RECT rect;
    ::GetWindowRect(mWindowHandle, &rect);
    return rect.top;
}


void Window::Y(int value)
{
    ::SetWindowPos(mWindowHandle, NULL, X(), value, NULL, NULL, SWP_NOZORDER | SWP_NOSIZE);
}


int Window::Width(void) const
{
    RECT rect;
    ::GetWindowRect(mWindowHandle, &rect);
    return rect.right - rect.left;
}


void Window::Width(int value)
{
    ::SetWindowPos(mWindowHandle, NULL, NULL, NULL, value, Height(), SWP_NOZORDER | SWP_NOMOVE);
}


int Window::Height(void) const
{
    RECT rect;
    ::GetWindowRect(mWindowHandle, &rect);
    return rect.bottom - rect.top;
}


void Window::Height(int value)
{
    ::SetWindowPos(mWindowHandle, NULL, NULL, NULL, Width(), value, SWP_NOZORDER | SWP_NOMOVE);
}


void Window::Destroy(void)
{
    // Destroy the window if the handle is valid
    if (mWindowHandle)
    {
        // First, destroy the window via system API
        ::DestroyWindow(mWindowHandle);

        // Set the window handle to NULL to avoid double destroying, this should be done in WM_DESTROY
        mWindowHandle = NULL;
    }

    // Unregister the class atom if the handle is valid
    if (mClassAtom)
    {
        ::UnregisterClassW((LPCWSTR)MAKELONG(mClassAtom, 0), GetModuleHandle(NULL));
        mClassAtom = NULL;
    }
}


LRESULT Window::WindowProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_ACTIVATEAPP:
        {
            if (wParam)
            {
                Activated.Raise(*this, EventArgs());
            }
            else
            {
                Deactivated.Raise(*this, EventArgs());
            }

            return FALSE;
        }

        case WM_CLOSE:
        {
            Closed.Raise(*this, EventArgs());
            return FALSE;
        }

        case WM_DESTROY:
        {
            // Raise the event before the message is done being procesed
            {
                Destroyed.Raise(*this, EventArgs());
            }

            // Remove the window
            {
                // Remove the entry from the global map
                if (!gWindowTable.empty())
                {
                    gWindowTable.erase(mWindowHandle);
                }

                // The window handle is no longer valid after this point
                mWindowHandle = NULL;
            }

            return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
        }

        case WM_ERASEBKGND:
        {
            // Return true if we handle the message
            return TRUE;
        }

        case WM_KILLFOCUS:
        {
            KillFocus.Raise(*this, EventArgs());
            return FALSE;
        }

        case WM_MOVE:
        {
            Moved.Raise(*this, MoveEventArgs(LOWORD(lParam), HIWORD(lParam)));
            return FALSE;
        }

        case WM_SETFOCUS:
        {
            SetFocus.Raise(*this, EventArgs());
            return FALSE;
        }

        case WM_SIZE:
        {
            switch (wParam)
            {
                case SIZE_MAXIMIZED:
                    Maximized.Raise(*this, EventArgs());
                    break;

                case SIZE_MINIMIZED:
                    Minimized.Raise(*this, EventArgs());
                    break;

                case SIZE_RESTORED:
                    Restored.Raise(*this, EventArgs());
                    break;

                default:
                    Sized.Raise(*this, SizeEventArgs(LOWORD(lParam), HIWORD(lParam)));
                    break;
            }

            return FALSE;
        }
    }

    return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
}


LRESULT CALLBACK Window::RaiseWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    if (!gWindowTable.empty())
    {
        if (gWindowTable.find(hWnd) != gWindowTable.end())
        {
            return gWindowTable[hWnd]->WindowProcedure(hWnd, uMsg, wParam, lParam);
        }
    }

    return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
